package com.makinolo.rlvforge
{
import com.google.maps.LatLng;
import com.makinolo.rlvforge.components.TimeGapSelector;
import com.makinolo.rlvforge.tacx.data.*;
import com.makinolo.rlvforge.tacx.file.*;
import com.netease.protobuf.Int64;
import com.netease.protobuf.UInt64;

import flash.events.*;
import flash.filesystem.*;
import flash.net.*;
import flash.utils.ByteArray;
import flash.xml.*;

import mx.collections.ArrayCollection;
import mx.collections.XMLListCollection;
import mx.controls.Alert;
import mx.events.CloseEvent;
import mx.events.MetadataEvent;
import mx.formatters.DateFormatter;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.http.HTTPService;
import mx.utils.StringUtil;

import net.duncanhall.gmaps.PolylineEncoder;

import session.GeoInfo;
import session.GeoInfo.GeoData;
import session.GroundType;
import session.LengthType;
import session.MainHeader;
import session.PartType;
import session.PendingSessionFile;
import session.ProfileData;
import session.ProfileData.ProfileSegment;
import session.RiderInfo;
import session.RiderInfo.Expertise;
import session.RiderInfo.Gender;
import session.RiderInfo.ZoneDef;
import session.SessionDefinition;
import session.SessionDefinition.Activity;
import session.SessionDefinition.SessionBlock;
import session.SessionType;
import session.SubscriptionType;
import session.TerrainInfo;
import session.TrainingZone;
import session.VideoInfo;
import session.VideoInfo.FrameDistanceMap;

[Event (name="PRJLoaded", type="flash.events.Event")]
[Event (name="PRJSaved", type="flash.events.Event")]
[Event (name="PRJExported", type="flash.events.Event")]
[Event (name="PRJChanged", type="flash.events.Event")]
[Event (name="TimeGapsFound", type="flash.events.Event")]


	
public class ProjectData extends EventDispatcher
{
	private const DIST_INTERVAL:Number = 10;	
	private const NOT_ASSIGNED:Number = 999999;
	private const MAX_TIME_BTW_POINTS:Number = 10;	// in seconds
	private const ELEVATION_API_POINTS_PER_REQUEST:Number = 500;	
	
	private var prjfile:File;
	private var stagename:String;
	private var stagedescription:String;	
	private var prfxmlData:XML;
	private var videofile:String;
	private var gpsfile:String;
	private var gpsfiletype:String;	
	private var videoprofile:VideoProfile;
	private var gpsdata:Array;
	private var gpsdatafixed:Array;
	private var pdata:Array;
	private var pdatafixed:Array;
	private var timemap:Array;
	private var distancemap:Array;
	private var totaltime:Number;
	private var totaldistance:Number;	
	private var totalpoints:int;
	private var maxheight:Number;
	private var minheight:Number;

	private var ready:Boolean;
	private var gpxstarttime:Number;
	
	private var elevationChangeStart:int;
	private var encoded:Array;

	private var correctTimeGaps:Boolean=false;
	private var timeGaps:ArrayCollection = new ArrayCollection();

	public function ProjectData()
	{
		this.minheight = 99999;
		this.maxheight = this.minheight+100;		
		this.ready = false;
	}

	public function isReady():Boolean
	{
		return this.ready;
	}

	public function open(file:File, vp:VideoProfile):void
	{				
		this.prjfile = file; 
		this.videoprofile = vp;

		// Abrimos el archivo de proyecto 	
		file.addEventListener(IOErrorEvent.IO_ERROR, onIOError)

		var fs:FileStream = new FileStream();
		fs.addEventListener(Event.COMPLETE, readProject);			
		fs.openAsync(file, FileMode.READ);	
	}
	
	public function create(vp:VideoProfile, videofilename:String, 
						   gpsfilename:String, gpsfiletype:String,
						   name:String=null, desc:String = null):void
	{
		this.prjfile = null;
		this.stagename = name;
		this.stagedescription = desc;
		this.videoprofile = vp;		
		this.prfxmlData = null;
		this.videofile = videofilename;
		this.gpsfile = gpsfilename;
		this.gpsfiletype = gpsfiletype;
		this.loadVideo(this.videoprofile);				
	} 
	
	public function modify(videofilename:String, gpxfilename:String, name:String=null, desc:String = null):void
	{		
		this.stagename = name;
		this.stagedescription = desc;					
		this.videofile = videofilename;
		this.gpsfile = gpxfilename;
		this.loadVideo(this.videoprofile);				
	} 	
		
	private function onIOError(e:Event):void
	{
		trace(e);
	}

	private function readProject(e:Event):void
	{
		try
		{
			this.prfxmlData = new XML(e.target.readUTFBytes(e.target.bytesAvailable));			
			// Obtenemos el nombre del GPX y del video
			this.stagename = this.prfxmlData.@course;
			this.stagedescription = this.prfxmlData.@description; 
			this.videofile = this.prfxmlData.@videosource;
			this.gpsfile = this.prfxmlData.@gpssource;		
			
		
			if (this.videofile == "" || this.gpsfile=="")
			{
				trace("prf no encontrado o mal formado");
			}
			else
			{	
				this.loadVideo(this.videoprofile);
			}
		}
		catch (e:ReferenceError)
		{
			trace("Errores en el formato XML del prf "+e);
		}				
		e.target.close();		
	}	


	private function loadVideo(vp:VideoProfile):void
	{		
		vp.addEventListener(MetadataEvent.METADATA_RECEIVED, videoReady);
		vp.bufferTime = 10;
		if (this.videofile.indexOf("/") == -1 && this.videofile.indexOf("\\") == -1)
			vp.source = this.prjfile.parent.nativePath + "/" + this.videofile;
		else
			vp.source = this.videofile;			
		vp.load();		
	}	

	private function videoReady(evt:Event):void
	{
		trace("video leido");
		this.videoprofile.removeEventListener(MetadataEvent.METADATA_RECEIVED, videoReady);
		this.totaltime = Math.ceil(evt.currentTarget.totalTime)
		timemap = new Array(this.totaltime);		
		
		readGPSFileStream();
	}
	
	private function readGPSFileStream()
	{
		var gpxsource:String = "";	
		var fs:FileStream = new FileStream();
		fs.addEventListener(Event.COMPLETE, readGPXData);	
		if (this.videofile.indexOf("/") == -1 && this.videofile.indexOf("\\") == -1)
			gpxsource = this.prjfile.parent.nativePath + "/" + this.gpsfile;
		else
			gpxsource = this.gpsfile;
		fs.openAsync(new File(gpxsource), FileMode.READ);	
	}
	
	private function readPRF():Boolean
	{
		if (this.prfxmlData!=null)
		{
			// reseteamos el timemap para que se adapte a lo que dice el prf
			// y se pueda recalcular bien con fixTimeMap
			unAssignTimeMap(0, timemap.length-1);
			var i:int=0;
			var pointList:XMLList = this.prfxmlData.segment; 
			for each (var point:XML in pointList) 
			{
				pdatafixed[i].setSlope(point.@slope);			
				pdatafixed[i].setSpeed(point.@speed);
				pdatafixed[i].setDistance(point.@distance);
				gpsdatafixed[i].setElev(point.@elevation);
				gpsdatafixed[i].setTime(gpsdatafixed[0].getTime() + (point.@time * 1000));
				if (point.@marker == 1)
					gpsdatafixed[i].setMarker(true);				
				mapTime(point.@time, i);
				
				if (point.infobox.@desc.length() > 0)
				{
					trace("Infobox -"+point.infobox.@desc+"-");
					gpsdatafixed[i].setComment(point.infobox.@desc);
					gpsdatafixed[i].setCommentType("INFOBOX");
				}
				if (point.course.@action.length() > 0)
				{
					gpsdatafixed[i].setComment(point.course.@desc);
					gpsdatafixed[i].setCommentType(point.course.@action);
				}
//trace("timemap["+Math.ceil(pointList[i].@time)+"]="+i);
				i++;
			}	
			this.totaldistance = pdatafixed[i-1].getDistance();
		}		
		trace("Total distance leida de PRF="+this.totaldistance);
		fixTimeMap();
		this.ready = true;
		// Una vez finalizadas todas la lecturas se da la señal al 
		// video para que pinte los datos del perfil
		this.videoprofile.setData(this);
		this.dispatchEvent(new Event("PRJLoaded"));	
		return true;	
	}

	private function readGPXData(e:Event):Boolean
	{
		var file:FileStream = FileStream(e.target);
		var xmlData:XML;
		default xml namespace =new Namespace( "http://www.topografix.com/GPX/1/1" );
			
		if (this.gpsfiletype == "gpx")
		{
			xmlData = new XML(file.readUTFBytes(file.bytesAvailable));
		}	
		else if (this.gpsfiletype == "nmea")
		{
			xmlData = convertNMEA2XML(file);
		}		
		return readGeoData(xmlData);
	}			
			
	private function readGeoData(xmlData:XML):Boolean
	{
	//	var file:FileStream = FileStream(e.target);
	//	default xml namespace =new Namespace( "http://www.topografix.com/GPX/1/1" );
	//	var xmlData:XML = new XML(file.readUTFBytes(file.bytesAvailable));
		//xmlData.addNamespace(new Namespace( "http://www.topografix.com/GPX/1/1" ));								
		var pointList:XMLList  = xmlData.trk.trkseg.trkpt; 
		if (pointList == null || pointList.length()==0)
		{
			default xml namespace =new Namespace("http://www.topografix.com/GPX/1/0" );
			pointList = xmlData.trk.trkseg.trkpt;
		}
		if (pointList == null || pointList.length()==0)
			return false;
			
		var initime:Number = Utils.parseUTCDate(pointList[0].time).time / 1000;
		gpxstarttime = initime;
		var endtime:Number = Utils.parseUTCDate(pointList[pointList.length()-1].time).time / 1000;
		this.totaltime = Math.round(endtime-initime);				
		this.totaldistance = 0;
		var deltatime:Number;
		var currtimefrom:Number;
		var currtimeto:Number;
		var elapsedtime:Number;
		var segslope:Number;
		var segspeed:Number;
		var departure:GISPoint;
		var destination:GISPoint;
		var distance:Number = 0;
		var startdist:Number = 0;
		var enddist:Number = 0;
		var pointcounter:int = 0;
		var auxdist:Number=0;
		var realdist:Number=0;
		var sumae:Number=0;
		var j:Number;		
		var timecorrection:Number=0;
		var timecorrectioncount:Number=0;
		var lastspeed:Number = 0;
		
		// Si el proyecto no tiene nombre intentamos tomar el del track
		if (this.stagename=="" || this.stagename==null)
			this.stagename = xmlData.trk.name;
		
		// Leemos los waypoints para sacar los INFOBOX y los courses
		var wptList:XMLList = xmlData.wpt; 
		var ibox:Array = new Array();
		var gp:GISPoint;
		for(k=0; k<wptList.length(); k++)
		{
			gp = new GISPoint(wptList[k].@lat, wptList[k].@lon, wptList[k].@ele, 0);
			gp.setComment(wptList[k].cmt);
			gp.setCommentType(wptList[k].name);
			ibox.push(gp);				
		}		
		
		distancemap = new Array();
		gpsdata = new Array();
		pdata = new Array();
		gpsdatafixed = new Array();		
		pdatafixed = new Array();
		unAssignTimeMap(0, timemap.length);
		timemap[0] = 0;
		
		// Recorremos todos los puntos del archivo. Si entre dos puntos hay mas de 10 metros
		// creamos un punto por cada 10 metros
		// No llegamos al ultimo punto porque estos se leen de dos en dos, origen y destino
		for(var k:int=0; k<pointList.length()-1; k++)
		{			
			departure = new GISPoint(pointList[k].@lat, pointList[k].@lon, pointList[k].ele.text(), Utils.parseUTCDate(pointList[k].time).time-timecorrection );
			destination = new GISPoint(pointList[k+1].@lat, pointList[k+1].@lon, pointList[k+1].ele.text(), Utils.parseUTCDate(pointList[k+1].time).time-timecorrection);
			distance =departure.distance(destination,true);
			if (distance>=1 && departure.hasElevation())
			{
				startdist = enddist;
				enddist = startdist + distance;
				
				if (! destination.hasElevation())
				{
					var cnt:Number = k+2;			
					while (pointList[cnt].ele.text() == "" && cnt<pointList.length()-1)
						cnt++;
					departure.setElev(pointList[cnt].ele.text());
				}		
				realdist += distance;
				
				currtimefrom = (departure.getTime() / 1000) - initime;
				currtimeto = (destination.getTime() / 1000) - initime;
				deltatime = currtimeto - currtimefrom;
				// If deltatime is greater than a certain treshold that means that the video may have been
				// edited and there are some cuts on the track timeline. What we do to fix it is to calculate
				// a probable real deltatime (last delta is a good option) and then use it as a correction
				// for future point times
				// Do not test it for the first frame as there is no lastspeed calculated yet
				if (Math.abs(deltatime) > MAX_TIME_BTW_POINTS && k>0)
				{
					var estimatedTime:Number = distance/lastspeed;									
					timecorrectioncount++;									
					if ( correctTimeGaps )
					{
						if (timeGaps[timecorrectioncount-1].correct)
						{
							timecorrection += (deltatime - estimatedTime)*1000;
							trace("Time correction ="+timecorrection);						
							trace("CORRECTING GAP of "+deltatime+" seconds from "+new Date(departure.getTime())+" to "+new Date(destination.getTime()));
							destination.setTime(destination.getTime()-timecorrection);
							currtimeto = (destination.getTime() / 1000) - initime;
							deltatime = estimatedTime;							
						}
					}
					else
					{
						trace("FOUND GAP of "+deltatime+" seconds from "+new Date(departure.getTime())+" to "+new Date(destination.getTime()));											
						// when not correcting time gaps, store them for later use
						timeGaps.addItem({depart:departure.getTime(), 
										  dest:destination.getTime(),
										  departLap: currtimefrom,
										  destLap: currtimeto,
										  lenght:deltatime, 
										  correct:false});	
					}
									
				}
				segslope = departure.slope(destination);
				segspeed = distance / deltatime;
				lastspeed = segspeed;	
				var gpxspeed = pointList[k].speed.text();
			
				// Lectura de waypoints a modo de infoboxes si los hay
				for(var m:int=0; m<ibox.length; m++)
				{										
					if (ibox[m].getLat() == departure.getLat() &&
						ibox[m].getLon() == departure.getLon() )
					{
						departure.setComment(ibox[m].getComment());
						departure.setCommentType(ibox[m].getCommentType());
					}
	
					if (destination.getCommentType()==null &&
						ibox[m].getLat() == destination.getLat() &&
						ibox[m].getLon() == destination.getLon() )
					{
						destination.setComment(ibox[m].getComment());
						destination.setCommentType(ibox[m].getCommentType());
					}
	
				}
			
				// Guardo el punto de partida 
				gpsdata[pointcounter] = departure.copy();	
				gpsdatafixed[pointcounter] = departure.copy();	
			
				if (gpsdata[pointcounter].getElev() > this.maxheight)
					this.maxheight = gpsdata[pointcounter].getElev();
				if (gpsdata[pointcounter].getElev() < this.minheight)
					this.minheight = gpsdata[pointcounter].getElev();
				//auxdist = totaldistance;
				
				if (distance <= DIST_INTERVAL)
				{
					elapsedtime = currtimeto-currtimefrom;
					pdata[pointcounter] = new com.makinolo.rlvforge.ProgramData(segslope, segspeed, auxdist);
					pdatafixed[pointcounter] = new com.makinolo.rlvforge.ProgramData(segslope, segspeed, auxdist);
					distancemap[Math.ceil(auxdist/DIST_INTERVAL)] = pointcounter;
					mapTime(sumae, pointcounter);
					pointcounter++;
					auxdist += distance;
					sumae += elapsedtime;
				}			
				else
				{				
					// Calculamos el tiempo transcurrido para los metros recorridos por cada 
					// iteracion a la velocidad actual		
					elapsedtime = DIST_INTERVAL/segspeed;
					for(j=startdist; j<enddist; j+=DIST_INTERVAL)
					{	
						pdata[pointcounter] = new com.makinolo.rlvforge.ProgramData(segslope, segspeed, auxdist);
						pdatafixed[pointcounter] = new com.makinolo.rlvforge.ProgramData(segslope, segspeed, auxdist);					
						distancemap[Math.round(auxdist/DIST_INTERVAL)] = pointcounter;
						mapTime(sumae,pointcounter);
						pointcounter++;
						gpsdata[pointcounter] = departure.travelTo( destination, j-startdist+DIST_INTERVAL, segspeed);	
						gpsdatafixed[pointcounter] = departure.travelTo( destination, j-startdist+DIST_INTERVAL, segspeed);						
			
						auxdist += DIST_INTERVAL;
						sumae += elapsedtime;
					}
					var left:Number = enddist - (j - DIST_INTERVAL);				
					sumae -= elapsedtime;
					elapsedtime = left/segspeed;
					auxdist -= DIST_INTERVAL;
					auxdist += left;				
					sumae += elapsedtime;
					pdata[pointcounter] =  new com.makinolo.rlvforge.ProgramData(segslope, segspeed, auxdist);	
					pdatafixed[pointcounter] =  new com.makinolo.rlvforge.ProgramData(segslope, segspeed, auxdist);					
					distancemap[Math.round(auxdist/DIST_INTERVAL)] = pointcounter;
					mapTime(sumae,pointcounter);
				}	
			}		
		}			
		
		if (pointcounter>=gpsdatafixed.length)
			pointcounter = gpsdatafixed.length-1;
		// Verificamos si el tiempo total es distinto del tiempo de video y si es asi
		// lo corregimos para que cuadren
		if (this.totaltime != this.timemap.length)
		{
			this.totaltime = this.timemap.length;			
			gpsdatafixed[pointcounter].setTime((initime + this.totaltime)*1000);
		}
		fixTimeMap();		

		this.totalpoints = pointcounter;
		this.totaldistance = pdatafixed[pointcounter].getDistance();		

		this.minheight -= 20;
		this.maxheight = this.minheight+100;

trace ("Leido GPX");
trace ("distancia total        = "+totaldistance);
trace ("distancia aux          = "+auxdist);
trace ("distancia real         = "+realdist);
trace ("distancia ultimo pdata = "+pdata[pointcounter].getDistance());

trace ("tiempo total GPS          ="+currtimeto);
trace ("tiempo total video        ="+timemap.length);
trace ("tiempo ultimo gpsdatafixed="+((gpsdatafixed[pointcounter].getTime()/1000)-initime));
trace ("suma de elapsed time      ="+ sumae);
trace ("tiempo total de pdata     ="+ this.totaltime);

trace ("ultimo distance map       ="+ distancemap.length+ " last index= " + distancemap[distancemap.length-1]);
		
		if (timecorrectioncount!=0 && !correctTimeGaps)
		{		
			this.dispatchEvent(new Event("TimeGapsFound"));			
		}
		// Cargar el resto de los datos del proyecto con readPRF
		return this.readPRF();					
	}
	
	public function applyTimeCorrection():void
	{
		correctTimeGaps = true;		
		readGPSFileStream();
	}
	
	public function hasFile():Boolean
	{
		return (this.prjfile != null);
	}

	public function save(file:File = null):void
	{
		// comprobamos si existe el archivo del proyecto
		if (file == null && this.prjfile != null)
			buildProfile(this.stagename, this.stagedescription, this.prjfile);
		else if (file != null)
		{
			buildProfile(this.stagename , this.stagedescription, file);
			this.prjfile = file;
		}
		else
			trace ("Error al guardar el proyecto, prjfile=null");
	}

	public function export():void
	{
		var fortiuspgmf:CatalystProgram = this.toPgmf();
		var fortiusrlv:RLVDefinition = this.toRlv();
		var pfile:File;
		var rfile:File;
		var ifile:File;
		
		// TODO: Crear estructura de directorios de Fortius RLV y meter cada archivo en su sitio 
		if (hasFile())
		{
			pfile = new File(this.prjfile.nativePath+".pgmf");
			rfile = new File(this.prjfile.nativePath+".rlv");
			ifile = new File(this.prjfile.nativePath+".CmdList.txt");				
		}
		else
		{
			pfile = new File("exported.pgmf");
			rfile = new File("exported.rlv");				
			ifile = new File("exported.CmdList.txt");			
		}
				
		fortiuspgmf.toFile(pfile);			
		fortiusrlv.toFile(rfile);
		fortiusrlv.infoboxToFile(ifile);
		
		// TODO: Lanzar evento para sacar un alert de que se han guardado con exito
	}

	public function exportGPX():void
	{		
		var gpx:String = this.toGpx();
		var gpxFile:File;
		
		gpxFile = new File(this.videofile+".gpx");
			
		var byteArray:ByteArray = new ByteArray();
		byteArray.writeUTFBytes(gpx);
		var fs:FileStream = new FileStream();
		fs.open(gpxFile, FileMode.WRITE);
		fs.writeBytes(byteArray, 0, byteArray.length);
		fs.close();
	}
	
	// Exports a demo sessionDefinition to test the video in BSim
	public function exportSessionDefinitionPB():void
	{				
		var sdb:SessionDefinition = new SessionDefinition();               
        sdb.sessionId = 0;
        sdb.userId = 3;
        sdb.activity = Activity.CYCLING;
        
        sdb.creationDate = UInt64.parseUInt64( new Date().time.toString() );
        sdb.downloadDate = UInt64.parseUInt64(new Date().time.toString());
        sdb.limitDate = UInt64.parseUInt64( (new Date().time + 360000000).toString());		// 100 hours after
        sdb.softwareVersion = "RLVForge";
        //sdb.totalTime = this.totaltime * 1000;  // for sessions limited by time
        sdb.totalTime = 0;
        sdb.totalDistance = this.totaldistance;
        sdb.sessionType = SessionType.INDOOR;
        //sdb.sessionMode = SessionMode.       
		// Session blocks
		var sb:SessionBlock = new SessionBlock();
		sb.partLength = 1000;
		sb.partType = PartType.WARM_UP;
		sdb.block.push(sb);		
		
		sb = new SessionBlock();
		sb.partLength = this.totaldistance;
		sb.partType = PartType.MAIN;		
		sdb.block.push(sb);		
		
		sb = new SessionBlock();
		sb.partLength = 1000;
		sb.partType = PartType.COOL_DOWN;		
		sdb.block.push(sb);				
		
		// Rider Info
		var rib:RiderInfo = new RiderInfo();
        rib.riderId = 3
        rib.name = "RLVForge"
        rib.age = 30;
        rib.gender = Gender.MALE;
        rib.weight = 83000;
        rib.height = 0;
        rib.maxHr = 180;
        rib.restHr = 50;
        rib.expertiseLevel = Expertise.ACTIVE_PLUS;
        rib.averagePower = 175;
        
        // Write training zone ranges for the user
    	var zdb:ZoneDef = new ZoneDef();
    	zdb.zone = TrainingZone.STOP;
        zdb.upperHeartrate = 87;
        zdb.lowerHeartrate = 85;
        zdb.upperPower =2;
        zdb.lowerPower =0;                 
        rib.zone[0] = zdb;
        
        zdb = new ZoneDef();
    	zdb.zone =TrainingZone.RECOVERY;
        zdb.upperHeartrate =109;
        zdb.upperHeartrate =87;
        zdb.upperPower =138;
        zdb.lowerPower =108;                 
        rib.zone[1] = zdb;
        
        zdb = new ZoneDef();
    	zdb.zone =TrainingZone.LONG_DISTANCE;
        zdb.upperHeartrate =132;
        zdb.upperHeartrate =109;
        zdb.upperPower =187;
        zdb.lowerPower =138;                 
        rib.zone[2] = zdb;
         
		zdb = new ZoneDef();
    	zdb.zone =TrainingZone.RITHM;
        zdb.upperHeartrate =163;
        zdb.upperHeartrate =132;
        zdb.upperPower =224;
        zdb.lowerPower =187;                 
        rib.zone[3] = zdb;
        
        zdb = new ZoneDef();
    	zdb.zone =TrainingZone.THRESHOLD;
        zdb.upperHeartrate =182;
        zdb.upperHeartrate =163;
        zdb.upperPower =261;
        zdb.lowerPower =224;                 
        rib.zone[4] = zdb;
        
        zdb = new ZoneDef();
    	zdb.zone =TrainingZone.VO2MAX;
        zdb.upperHeartrate =192;
        zdb.upperHeartrate =182;
        zdb.upperPower =298;
        zdb.lowerPower =261;                 
        rib.zone[5] = zdb;
        
        zdb = new ZoneDef();
    	zdb.zone =TrainingZone.ACIDOTIC;
        zdb.upperHeartrate =193;
        zdb.upperHeartrate =192;
        zdb.upperPower =372;
        zdb.lowerPower =298;                 
        rib.zone[6] = zdb;

		// Include subscription information       
		rib.subscriptionType = SubscriptionType.GOLD; 
		rib.subscriptionEndDate = UInt64.parseUInt64( (new Date().time + 360000000).toString());
		
        // End of rider info
        sdb.rider[0] =  rib; 
                              
        var pdb:ProfileData = new ProfileData();
        pdb.profileName = this.stagename;
        pdb.profileDescription = this.stagedescription;
        pdb.slopeCorrection = 0;
        pdb.initAltitude = this.gpsdatafixed[0].getElev();        
        pdb.profileId = 1;
        
        var lastdistance:Number = 0;
        var prevslope:Number = pdatafixed[0].getSlope();        
        var timeSpentInsegment:Number = 0;
        var speedVariationThreshold:Number = 1.4; // In m/s  5Kmh = 1.4 m/s       
		for(var i:int=1; i<this.pdatafixed.length; i++)		 			    		
        {   
        	timeSpentInsegment += (pdatafixed[i].getDistance() - pdatafixed[i-1].getDistance()) / pdatafixed[i-1].getSpeed();
        	
        	if(pdatafixed[i].getSlope() != prevslope 
        	   || pdatafixed[i].getSpeed()>pdatafixed[i-1].getSpeed()+speedVariationThreshold ||  pdatafixed[i].getSpeed()<pdatafixed[i-1].getSpeed()-speedVariationThreshold )
        	{
            	var pSeg:ProfileSegment = new ProfileSegment();            
            	pSeg.distance = pdatafixed[i].getDistance() - lastdistance;
				pSeg.slope = prevslope / 100;				                       
            	pSeg.time = timeSpentInsegment * 1000;
            	pdb.segment.push(pSeg);
            	lastdistance = pdatafixed[i].getDistance(); 
            	timeSpentInsegment = 0;           	
            }
            prevslope = pdatafixed[i].getSlope();
        }

        var tib:TerrainInfo = new TerrainInfo();            
        tib.profilePlayType = LengthType.DISTANCE;                     
		var tempvideo:VideoInfo = new VideoInfo();
		tempvideo.videoId = 1;
		var wmvname:String = this.videofile.substring(this.videofile.lastIndexOf("/")+1);
		wmvname = wmvname.substring(0, wmvname.lastIndexOf("."))+".wmv"; 
		tempvideo.name = wmvname;
		tempvideo.url = "http://www.bkool.com/";    
		tempvideo.snapshotUrl = "http://www.bkool.com/";                                
        tempvideo.framerate = 25;    
        tempvideo.hres = 0;
        tempvideo.vres = 0;    
        
        var fdmap:FrameDistanceMap = new FrameDistanceMap();
        fdmap.distance = this.totaldistance;
        fdmap.frameNumber = 0;
        tempvideo.frameDistance.push(fdmap);
        
		tib.video = tempvideo;		
        tib.ground = GroundType.ROAD; 
        tib.windDir = 0;
        tib.wind = 0; 
        tib.drafting = true;
        tib.lapCount = 0;
        tib.profile = pdb;                
                                                              
		var geoInfo:GeoInfo = new GeoInfo();
		geoInfo.riderId = 3;
		geoInfo.sessionId = 1;
		geoInfo.checkSum = new Int64(1);				                                                             
        for( i=0; i<gpsdatafixed.length; i++)
        {
        	var geo:GeoData = new GeoData();
        	geo.elevation = gpsdatafixed[i].getElev();
        	geo.latitude = gpsdatafixed[i].getLat();
			geo.longitude = gpsdatafixed[i].getLon();        	
        	geoInfo.gpsData[i] = geo;	
        }
        tib.geo = geoInfo;
        sdb.terrain = tib;
        
        // PSF
        var psf:PendingSessionFile = new PendingSessionFile();
        var mh:MainHeader = new MainHeader();
        mh.fileVersion = 100;
        mh.softwareId = 11110260;
        mh.signature = "GeneratedByRLVForge!";
        psf.mainHeader = mh;
        psf.sessionDefinition = sdb;
        
        var psfFile:File;		
		psfFile = new File(this.videofile+".psf");		
        
		var fs:FileStream = new FileStream();
		fs.open(psfFile, FileMode.WRITE);
		psf.writeTo(fs);			
		fs.close();
	}

	private function buildProfile(sname:String, sdesc:String, file:File):void
	{
		default xml namespace =new Namespace("");		
		var prf:XML = <profile></profile>;
		prf.@course = sname;
		prf.@description = sdesc;		
		prf.@videosource = this.videofile;
		prf.@gpssource = this.gpsfile;
		var segment:XML;
		var content:String = "";
		var itime:Number = gpsdatafixed[0].getTime();
		// Slope segments
		for (var i:int=0; i<=this.totalpoints; i++)
		{
			segment = <segment/>;
			segment.@distance = pdatafixed[i].getDistance().toFixed(4);
			segment.@slope = pdatafixed[i].getSlope().toFixed(2);
			segment.@elevation = gpsdatafixed[i].getElev().toFixed(1);
			segment.@speed = pdatafixed[i].getSpeed().toFixed(4);
			segment.@time = (gpsdatafixed[i].getTime()-itime)/1000;
			if (gpsdatafixed[i].isMarker())
				segment.@marker = 1;
			else
				segment.@marker = 0;
				
			if (gpsdatafixed[i].getCommentType() != null)
			{
				trace(gpsdatafixed[i].getCommentType());
				if (gpsdatafixed[i].getCommentType() == "INFOBOX" )
					segment.infobox.@desc = gpsdatafixed[i].getComment();
				else
				{
					segment.course.@action = gpsdatafixed[i].getCommentType();
					segment.course.@desc = gpsdatafixed[i].getComment();									
				}
			}					
			
			prf.appendChild(segment);
		}
		
		/*var file:File = File.desktopDirectory.resolvePath(sname+".csv");
		var fs:FileStream = new FileStream();
		fs.open(file, FileMode.WRITE);
		fs.writeUTFBytes(content);
		fs.close();*/
		
		//var file = File.desktopDirectory.resolvePath(sname+".gpx.prf");		
		var fs:FileStream = new FileStream();
		fs.open(file, FileMode.WRITE);
		fs.writeUTFBytes("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
		fs.writeUTFBytes(prf.toString());
		fs.close();
		
		//return content;
	}
	
	public function getTotalTime():Number
	{
		return this.totaltime;
	}
	
	public function getCurrentSlope(time:Number):Number		
	{
		var d:Number = timeToDistance(time);
		return pdata[distancemap[Math.floor(d/DIST_INTERVAL)]].getSlope();
	}
	public function getCurrentSlopeFixed(time:Number):Number		
	{
		var d:Number = timeToDistance(time);		
		return pdatafixed[distancemap[Math.floor(d/DIST_INTERVAL)]].getSlope();
	}
	
	public function getCurrentSpeed(time:Number):Number
	{
		var ndx:int = timemap[Math.floor(time)];
		return pdatafixed[ndx].getSpeed() * 3.6;
	}
	
	public function getCurrentDistance(time:Number):Number
	{		
		return timeToDistance(time);
	}	
	public function getCurrentHeight(time:Number):Number
	{
		var d:Number = timeToDistance(time);
		return gpsdata[distancemap[Math.floor(d/DIST_INTERVAL)]].getElev();
	}			
	public function getCurrentHeightFixed(time:Number):Number
	{
		var d:Number = timeToDistance(time);
		return gpsdatafixed[distancemap[Math.floor(d/DIST_INTERVAL)]].getElev();
	}	
			
	public function getSlopeBetween(itime:int, etime:int):Number
	{
		if (itime < etime)
		{
			if (etime>this.totaltime)
				etime = this.totaltime;
			var d1:Number = timeToDistance(itime);			
			var d2:Number = timeToDistance(etime);
			return gpsdatafixed[distancemap[Math.floor(d1/DIST_INTERVAL)]].slope(gpsdatafixed[distancemap[Math.floor(d2/DIST_INTERVAL)]]);
		}
		return -9999;
	}
	
	public function getSlopeBetweenDistance(d1:Number, d2:Number):Number
	{
		if (d1 < d2 && d1>=0 && d2>=0)
		{
			if (d2>this.totaldistance)
				d2=this.totaldistance;
				
			var sl:Number = gpsdatafixed[distanceToIndex(d1)].slope(gpsdatafixed[distanceToIndex(d2)]);
		//trace("getSlopeBetweenDistances d1="+d1+" d2="+d2+" slope="+sl);

			return sl;
		}
		return -9999;
	}
	
	
	public function getDistanceBetween(itime:int, etime:int):Number
	{
		if (itime < etime && itime<gpsdata.length && etime<gpsdata.length)
		{
			var dist:Number = 0;
			for (var i:int=itime; i<etime-1; i++)
				dist += gpsdata[i].distance(gpsdata[i+1], true);
			return dist;
		}
		return -1;
	}
	
	// Saca la altura mínima entre dos indices de pdata dados
	public function getMaxMinHeight(st:int, end:int):Array
	{
		var minval:Number = 99999;
		var maxval:Number = -99999;
		if(end>this.totalpoints)
			end = this.totalpoints;
		for (var i:int=st; i<end; i++)
		{
			if (gpsdatafixed[i].getElev()<minval)
				minval = gpsdatafixed[i].getElev();
			if (gpsdatafixed[i].getElev()>maxval)
				maxval = gpsdatafixed[i].getElev();
		}
		var ret:Array = [minval, maxval];
		return ret;
	}

	public function distanceToIndex(distance:Number):int
	{
		var idx:Number = Math.ceil(distance/DIST_INTERVAL);
		if (idx>distancemap.length-1)
			idx = distancemap.length-1;
		return distancemap[idx];
	}		
	
	private function relativeTime(time:Number):Number
	{
		return (time - gpsdatafixed[0].getTime()) / 1000;
	}
	
	// Debido a fallos en la seleccion de tramos hay algunos valores de pendiente
	// que no son coherentes con los precedentes ni los posteriores. Con este 
	// metodo los corregimos para suavizar esos pequeños desperfectos
	public function fixSlopes():void
	{
		var dh:Number;
		var dist:Number;
		var oldslope:Number, newslope:Number ;
		var lasth:Number = gpsdatafixed[0].getElev();
		for(var i:int=1; i<pdatafixed.length-1; i++)
		{
			oldslope = pdatafixed[i].getSlope();
			dist = pdatafixed[i].getDistance() - pdatafixed[i-1].getDistance();
			if (oldslope != pdatafixed[i-1].getSlope() &&
				oldslope != pdatafixed[i+1].getSlope() )
			{
				newslope = pdatafixed[i-1].getSlope(); 
				pdatafixed[i].setSlope(newslope);							
			}
			// Recalculamos la altura 
			dh = dist * (pdatafixed[i-1].getSlope()/100); 
			gpsdatafixed[i].setElev(gpsdatafixed[i-1].getElev()+ dh);
		}
		// gpsdatafixed tiene un elemento mas que pdatafixed, por lo que falta por recalcular
		// la altura del ultimo gpsdatafixed		 

		// TODO: Esto es algo raro, porque no hay datos para calcular la ultima distancia, lo que me 
		// hace sospechar que hay algo que estoy calculando mal, que no estoy empezando donde debo o
		// que en realidad gpsdatafixed deberia tener el mismo tamaño que pdatafixed
		gpsdatafixed[i].setElev(gpsdatafixed[i-1].getElev()+ dh);
		
			
		
		this.videoprofile.refreshBuffers();
	}
	
	// Puede que en ocasiones haya elementos del array timemap que no estén rellenos
	// o que esten mapeados a elementos inexistentes. Con esta funcion los arreglamos
	private function fixTimeMap():void
	{
		var countna:int = 0,
			countmp:int = 0;
		timemap[0] = 0;
		for(var i:int=1; i<timemap.length-1; i++)
		{
			if (timemap[i] == NOT_ASSIGNED)
			{
				timemap[i] = timemap[i-1];
				countna++;
			}
			else if (timemap[i] > timemap[i+1])
			{
				timemap[i] = timemap[i-1];
				countmp++;
			}				
		}
		if (countna>0 || countmp>0)
			trace("Fixed "+countna+"/"+timemap.length+" not assigned mappings and "+countmp+" misplaced");
	}		
	
	private function unAssignTimeMap(start:int, end:int):void
	{
		for (var i:int=start; i<end; i++)
			mapTime(i, NOT_ASSIGNED);		
	}
	
	
	public function timeToDistance(time:Number):Number
	{
		if (Math.floor(time) > timemap.length-1)
			time = timemap.length-1;
		var ndx:int = timemap[Math.floor(time)];
		if (gpsdatafixed[ndx]!=null)
		{
			var fromtime:Number = (gpsdatafixed[ndx].getTime() - gpsdatafixed[0].getTime())/1000;
			var d:Number = pdatafixed[ndx].getDistance();
			d += pdatafixed[ndx].getSpeed() * (time-fromtime);	
if (time-fromtime < 0)
{
trace("fromtime="+(time-fromtime)+" ndx="+ndx+" distance="+	pdatafixed[ndx].getDistance()+" time="+time );
trace("timemap["+Math.floor(time)+"]="+timemap[Math.floor(time)]+" gpstime="+relativeTime(gpsdatafixed[ndx].getTime()));	
	
}

		}
		return d;
	}
	
	
	private function mapTime(time:Number, idx:int):void
	{
		if(time>this.totaltime)
			time = this.totaltime;
		else
			time = Math.ceil(time);
		timemap[time] = idx;			
	}
	
	public function timeToIndex(time:int):int
	{
		return timemap[time];
	}


	public function deleteTrackPoint(idx:int):void
	{
		// Para borrar un punto del track necesitamos eliminarlo del pdata, del pdatafixed
		// del gpxdata y del gpxdatafixed.
		pdata.splice(idx, 1);
		pdatafixed.splice(idx, 1);
		gpsdata.splice(idx, 1);
		gpsdatafixed.splice(idx, 1);
		this.totalpoints--;
		
		// Despues de eliminarlos hay que recalcular los tiempos y velocidad entre el punto
		// anterior y el posterior
		
		this.dispatchEvent(new Event("PRJChanged"));	
	}
				
	/**
	 * Metodos GIS
	 * 
	 */
	public function updateGISPointTime(time:Number, idx:int, recalc:Boolean):Boolean
	{
		// Nunca ponemos un marker en el primer punto del track, no tiene sentido
		// y rompemos el algoritmo de abajo		
		if (time==0 || idx==0)
			return false;
		
		if (recalc)
		{
			// buscamos hacia atras el primer punto que sea un marker
			// Los markers son puntos del track que han sido marcados manualmente con un tiempo correcto
			// y sirven para calcular la velocidad del video.
			var travel:Number = 0;
			var elaptime:Number = 0;
			var startidx:int = idx-1;		
			var itime:Number = gpsdatafixed[0].getTime();
			while (startidx>0 && !gpsdatafixed[startidx].isMarker())
				startidx--;			
			var starttime:Number = relativeTime(gpsdatafixed[startidx].getTime());			
			var dtime:Number = time - starttime;  // en segundos
			var ddist:Number = pdatafixed[idx].getDistance() - pdatafixed[startidx].getDistance();  // en metros
			var newspeed:Number;
			if (dtime!=0)
				newspeed = ddist / dtime; // en metros por segundo
			else
				newspeed = 0;
	trace("Hacia atras");	
	trace("starttime="+starttime+" time="+time+" startidx="+startidx+" endidx="+idx);		
	//trace("dtime="+dtime+" ddist="+ddist+" newspeed="+newspeed+" oldspeed="+pdatafixed[startidx].getSpeed());
	
			// Reseteamos los valores de timemap para que se autogeneren los que no
			// sean rellenados por el bucle siguiente
			unAssignTimeMap(Math.floor(starttime), Math.floor(time) );
	
			if(startidx!=0)
				startidx++;
			for (var i:int= startidx; i<=idx; i++)
			{
				pdatafixed[i].setSpeed(newspeed);
				if (i>0)
					travel = pdatafixed[i].getDistance() - pdatafixed[i-1].getDistance();
				else 
					travel = 0;
				elaptime +=  (travel / newspeed);
				gpsdatafixed[i].setTime(itime + (starttime*1000) + (1000 *elaptime));
				mapTime(elaptime + starttime, i);
			}
		
			// Buscamos hacia delante hasta el siguiente marker
			var endidx:int = idx+1;
			while (!gpsdatafixed[endidx].isMarker() && endidx<this.totalpoints-1)
				endidx++;		
			var endtime:Number = timemap.length;;
			if (endidx!=this.totalpoints-1)
				endtime = relativeTime(gpsdatafixed[endidx].getTime());
			if (endtime>this.totaltime)
				endtime = this.totaltime;
	
			dtime = endtime - time;		
			ddist = pdatafixed[endidx].getDistance() - pdatafixed[idx].getDistance();
			newspeed = ddist / dtime;		
	trace("Hacia delante");
	trace("time="+time+" endtime="+endtime+" startidx="+idx+" endidx="+endidx);				
	//trace("dtime="+dtime+" ddist="+ddist+" newspeed="+newspeed+" oldspeed="+pdatafixed[startidx].getSpeed());
	
			// Reseteamos los valores de timemap para que se autogeneren los que no
			// sean rellenados por el bucle siguiente
			unAssignTimeMap(Math.floor(time+1), Math.floor(endtime) );			
			elaptime = 0;
			for (i= idx+1; i<=endidx; i++)
			{
				pdatafixed[i].setSpeed(newspeed);
				if (i>0)
					travel = pdatafixed[i].getDistance() - pdatafixed[i-1].getDistance();
				else 
					travel = 0;
				elaptime += (travel / newspeed);			
				gpsdatafixed[i].setTime(itime + (time*1000) + (1000 * elaptime));
				mapTime(elaptime + time, i);
			}
		}
		// El tiempo que recibimos corresponde al punto cuyo indice se proporciona
		gpsdatafixed[idx].setMarker(true);

		fixTimeMap();
		
		// Repintamos los buffers de perfil
		/*
		repaintBuffer(bufferpointer);
		repaintBuffer((bufferpointer+1)%2);
		this.setTime(time);*/
		this.dispatchEvent(new Event("PRJChanged"));
		return true;
	}
	
	public function smoothSlopes(gap:Number = 100):void
	{
		// La funcion de suavizado recibe como parámetro una distancia en metros
		// que será la que use para calcular las pendientes cada X metros en funcion 
		// de la altura en los puntos que toquen.
		
		var count:int=0;
		var deltadist:Number=0;
		var lastdist:Number = 0;
		var h:Number = 0;
		var lasth:Number = this.getOriginalElevAt(0);
		var slope:Number = 0;
		var initindex:int = 0;
		for (var i:int=0; i<this.totalpoints; i++)		
		{
			deltadist = this.getDistanceAt(i) - lastdist;
			if (deltadist >= gap)
			{
				h = this.getOriginalElevAt(i);
				// No utilizo getSlopleBetween* porque es mas preciso usar los indices
				// en lugar de los tiempos o las distancias
				slope = (h - lasth)/deltadist * 100;
				
if (slope>10)
{
	trace("slope= "+slope+" h="+h+" lasth="+lasth+" deltadist="+deltadist);
}
				this.correctSlope(initindex, i, slope );
				deltadist = 0;
				lastdist = this.getDistanceAt(i);
				lasth = h;
				initindex = i;
			}
		}
		this.correctSlope(initindex, this.totalpoints, slope);
		// Media movil de 5 valores
/*		var count:int=0;
		var suma:Number=0;
		for (var i:int=0; i<this.totalpoints-1; i+=5)
		{
			count = 0;
			suma = 0;
			var j:int;
			for(j = i; j<i+5 && j<this.totalpoints; j++)
			{
				if (j>=0 && j<this.totalpoints)
				{
					suma += pdata[j].getSlope();
					count ++;
				}
			}
			trace("media movil="+(suma / count));		
			this.correctSlope(i, j, suma / count );
		}*/
	}
	
	public function setElevationFromGoogle(startidx:int, endidx:int):void
	{
		// Hay que acceder al API web de google maps ya que el componente para flex no proporciona altitudes				
		var requestNeeded:Number = Math.floor((endidx - startidx) / ELEVATION_API_POINTS_PER_REQUEST)+1;
		encoded = new Array();
		for (var j:Number=0; j<requestNeeded; j++)		
		{
			var points:Array = new Array();					
			var loopstart:int = startidx + (j*ELEVATION_API_POINTS_PER_REQUEST);
			var i:int = loopstart;
			while (i<endidx && i<loopstart+ELEVATION_API_POINTS_PER_REQUEST)		
			{
				points.push(new LatLng(gpsdatafixed[i].getLat(), gpsdatafixed[i].getLon()));
				i++;			
			}			
			encoded.push(PolylineEncoder.fromPoints(points));						
		}	
		
		elevationChangeStart = startidx;
		callGoogleElevationAPI(encoded.shift());	
	}
	
	private function callGoogleElevationAPI(encoded:String):void
	{
		var url:String = "http://maps.google.com/maps/api/elevation/xml"		
		var serviceParameters: Object = {};
		//serviceParameters["path"] = path;
 		serviceParameters["path"] = "enc:"+encoded;
		serviceParameters["samples"] = ELEVATION_API_POINTS_PER_REQUEST;
		serviceParameters["sensor"]=false;		
		var remote:HTTPService = new HTTPService();
		remote.url = url;		
		remote.resultFormat = "object";
		remote.addEventListener(ResultEvent.RESULT, onElevationRead);
		remote.addEventListener(FaultEvent.FAULT, onElevationReadFailed);
		remote.send(serviceParameters);
	}
	
	private function onElevationRead(event:ResultEvent):void
	{
		var response:Object = event.result.ElevationResponse;
		var index:int = elevationChangeStart;
		for each (var point:Object in response.result)
		{
			if (index<this.totalpoints)
			{
				gpsdatafixed[index].setElev(Number(point.elevation));
				index++;
			}				
		}
		this.recalculateProfile(elevationChangeStart,index-elevationChangeStart); 
		this.dispatchEvent(new Event("PRJChanged"));
		if (encoded.length>0)
		{
			elevationChangeStart += ELEVATION_API_POINTS_PER_REQUEST;		
			callGoogleElevationAPI(encoded.shift());
		}
	}
	private function onElevationReadFailed(event:FaultEvent):void
	{		
		trace (event.fault);
	}
	
	
	public function unsetMarker(idx:int):void
	{
		gpsdatafixed[idx].setMarker(false);
	}
	
	public function getCurrentGISPoint(time:Number):GISPoint
	{		
		var d:Number = timeToDistance(time);
		if (d>this.totaldistance)
			trace("OJO!!!! mapeando tiempo="+time+" me sale una distancia ="+d+" superior a la total = "+this.totaldistance);
		return gpsdatafixed[distancemap[Math.floor(d/DIST_INTERVAL)]];
//trace("getCurrentGISPoint ="+timemap[Math.round(time)]);		
//		return gpsdatafixed[timemap[Math.round(time)]];
	}
	
	// Busca el punto dentro del track que mas cerca esté del que se pasa como parámetro
	// y devuelve su índice
	public function findNearestTrackPoint(ll:LatLng):int
	{
		var target:GISPoint = new GISPoint(ll.lat(), ll.lng(),0,0);
		var nearest:int = 0;		
		var mindistance:Number = gpsdatafixed[0].distance(target, false);
		var currdist:Number = 0; 
		for(var i:int=1; i<this.totalpoints; i++)
		{
			currdist = gpsdatafixed[i].distance(target, false);
			if (currdist < mindistance)
			{
				mindistance = currdist;
				nearest = i;
			}
		}
		
		return nearest;
	}
	
	public function getPointArray():Array
	{
		var ret:Array = new Array();
		for (var i:int=0; i<this.totalpoints; i++)
		{
			ret[i] = new LatLng( gpsdatafixed[i].getLat(), gpsdatafixed[i].getLon() );
		}
		return ret;
	}
	
	public function getTimeMarkers():Array
	{
		var ret:Array = new Array();
		for (var i:int=0, cnt:int=0; i<this.totalpoints; i++)
		{
			if (gpsdatafixed[i].isMarker())
				ret[cnt++] = new LatLng(gpsdatafixed[i].getLat(), gpsdatafixed[i].getLon() );
		}
		return ret;
		
	}
	
	public function getInfoBoxMarkers():Array
	{
		var ret:Array = new Array();
		for (var i:int=0, cnt:int=0; i<this.totalpoints; i++)
		{
			if (gpsdatafixed[i].getCommentType() == "INFOBOX")
				ret[cnt++] = new LatLng(gpsdatafixed[i].getLat(), gpsdatafixed[i].getLon() );
		}
		return ret;
	}
	
	public function getPointAt(index:int):LatLng
	{
		return new LatLng( gpsdatafixed[index].getLat(), gpsdatafixed[index].getLon() );
	}
	public function setLatAt(index:int, lat:Number):void
	{
		gpsdatafixed[index].setLat(lat);		
	}
	public function setLonAt(index:int, lon:Number):void
	{
		gpsdatafixed[index].setLon(lon);		
	}
	
	public function getDistanceAt(index:int):Number
	{
		return pdatafixed[index].getDistance();
	}
	
	public function getTimeAt(index:int):Number
	{
		for(var i:int=0; i<timemap.length; i++)
			if (timemap[i]==index)
				return i;
		return 0;
	}
	public function getElevAt(index:int):Number
	{
		return gpsdatafixed[index].getElev();
	}
	
	public function setElevAt(index:int, elevation:Number):void
	{
		gpsdatafixed[index].setElev(elevation);
	}	
	
	public function getOriginalElevAt(index:int):Number
	{
		return gpsdata[index].getElev();
	}
	
	public function getSlopeAt(index:int):Number
	{
		return pdatafixed[index].getSlope();
	}
	private function setSlopeAt(index:int, slope:Number):void
	{
		pdatafixed[index].setSlope(slope);
	}
	private function setSpeedAt(index:int, speed:Number):void
	{
		pdatafixed[index].setSpeed(speed);
	}
			
	public function correctSlope(beginidx:int, endidx:int, newslope:Number):void
	{
		if (beginidx>=0 && endidx>0 && endidx>=beginidx)
		{
			var dist:Number;
			var dh:Number;
			for (var i:int=beginidx; i<endidx; i++)
			{
				dist = this.getDistanceAt(i+1) - this.getDistanceAt(i);
				dh = dist * (newslope/100);
				this.setElevAt(i+1, this.getElevAt(i) + dh);
				this.setSlopeAt(i, newslope);
			}
			if (endidx<this.getTotalPoints())
			{
				dh = this.getElevAt(endidx) - this.getElevAt(endidx+1);			
				for (i=endidx+1; i<this.getTotalPoints(); i++)
				{
					this.setElevAt(i, this.getElevAt(i) + dh);
				}
			}			
		}		
	}
	
	public function constantSlope(newslope:Number):void
	{
		for (var i:int=0; i<this.totalpoints; i++)
		{
			var dist:Number = this.getDistanceAt(i+1) - this.getDistanceAt(i);
			var dh:Number = dist * (newslope/100);
			this.setElevAt(i+1, this.getElevAt(i) + dh);
			this.setSlopeAt(i, newslope);
		}
	}
	
	public function bkoolFilters():void
	{
		//fillInElevation(track);
        //accelerationFilter(track, maxAcceleration);
        //duplicateFilter(10);
        
        //slopeThresholdFilter(track, slopeTolerance, minSlopeChangeDistance);
        //discard(track, maxSpeed, maxSlope);
        //simplify(track, slopeTolerance);
	}
	
	private function duplicateFilter( minDistance:Number)
    {
    	var distance:Number = 0;
    	var previousPoint:GISPoint = gpsdatafixed[0];
    	var pointIsValid:Boolean;
    	var validPoints:ArrayCollection = new ArrayCollection();
    	validPoints.addItem(previousPoint);
    	for (var i:int=1; i<gpsdatafixed.length; i++)
    	{
    		pointIsValid = true;
    		distance = previousPoint.distance(gpsdatafixed[i], true);
    		if (distance<minDistance)    		
    			pointIsValid = false
    		
    		if (pointIsValid)
    		{
    			validPoints.addItem(gpsdatafixed[i]);
    			previousPoint = gpsdatafixed[i];
    		}    		
    	}
    	if (validPoints.length>0)
    	{
    		gpsdatafixed = validPoints.toArray();
    		
    	}
      
    }
    
	
	public function recalculateProfile(beginidx:int, endidx:int):void
	{
		if (beginidx>=0 && endidx>=0 && endidx>=beginidx && endidx<this.getTotalPoints())
		{
			var dist:Number;
			var dh:Number;
			for (var i:int=beginidx; i<endidx; i++)
			{
				dist = this.getDistanceAt(i+1) - this.getDistanceAt(i);				
				this.setSlopeAt(i, gpsdatafixed[i].slope(gpsdatafixed[i+1]));
				this.setSpeedAt(i, gpsdatafixed[i].speed(gpsdatafixed[i+1]));
			}
		}
	}
	
	public function dataAvailableAt(index:int):Boolean
	{
		return (gpsdatafixed[index]!=null && gpsdata[index]!=null);
	}
	
	public function getGISPointArrayCollection():ArrayCollection
	{
		var al:ArrayCollection = new ArrayCollection();
		var i:Number = 0;
		var t:Number = 0;
		for each (var gp:GISPoint in  gpsdatafixed)
		{
			t = (gp.getTime()/1000 - this.gpxstarttime);
			al.addItem({elevation:gp.getElev(), distance:pdatafixed[i].getDistance(), time:t, slope:pdatafixed[i].getSlope()});
			i++;
		}		
		return al;
	}
	
	public function getCommentTypeAt(index:int):String
	{
		return this.gpsdatafixed[index].getCommentType();
	}
	public function getCommentAt(index:int):String
	{
		return this.gpsdatafixed[index].getComment();
	}		
	
	
	public function getGridData():ArrayCollection
	{		
		var al:ArrayCollection = new ArrayCollection();
		var i:Number = 0;
		var t:Number = 0;
		for each (var gp:GISPoint in  gpsdatafixed)
		{
			t = (gp.getTime()/1000 - this.gpxstarttime);
			al.addItem({index:i,
						elevation:gp.getElev(), 
						distance:pdatafixed[i].getDistance(), 
						formattedtime:Utils.formatTime(t),
						time:t, 
						slope:pdatafixed[i].getSlope(),
						latitude:gp.getLat(),
						longitude:gp.getLon(),
						speed:pdatafixed[i].getSpeed(),						
						marker:gp.isMarker(),
						selected:false});
			i++;
		}		
		return al;
	}
	
	
	public function getProjectName():String
	{
		if (this.prjfile != null)
			return this.prjfile.name;
		else
			return "Unnamed";
	}
		
	public function getStageName():String
	{
		return this.stagename;
	}

	public function getStageDesc():String
	{
		return this.stagedescription;
	}
	public function getVideoPath():String
	{
		return this.videofile;
	}
	public function getGPSPath():String
	{
		return this.gpsfile;
	}
	
	/**
	 * Member getters
	 */
	public function getTotalPoints():Number
	{
		return this.totalpoints;	
	}
	
	public function getMinHeight():Number
	{
		return this.minheight;
	}
	public function getMaxHeight():Number
	{
		return this.maxheight;
	}	
	
	public function getTimeGaps():ArrayCollection
	{
		return this.timeGaps;
	}
	/**
	 *  Conversion to Tacx Data Formats
	 */
	 
	public function toPgmf():CatalystProgram
	{		
		var totalTimeDist:Number = 0;
		var totalTimeDistPlain:Number = 0;
		var altitudeStart:Number = -99999;
		var checkSum:Number = 0;
		var progvector:Array = new Array();		
		var elapseddistance:Number = 0.0; 
		var prevslope:Number = -99.0;
		var lastdistance:Number = 0.0;			
		
		prevslope = pdatafixed[0].getSlope();
		altitudeStart = gpsdatafixed[0].getElev();

		for(var i:int=0; i<this.pdatafixed.length-1; i++)		 			    
		{			
			if (prevslope != pdatafixed[i].getSlope())
			{
				elapseddistance = pdatafixed[i].getDistance() - lastdistance;
				progvector.push(new com.makinolo.rlvforge.tacx.data.ProgramData(elapseddistance, prevslope));
				prevslope = pdatafixed[i].getSlope();
				lastdistance = pdatafixed[i].getDistance();										
			}            
        }
        // Last segment processing	        
		elapseddistance = pdatafixed[i].getDistance() - lastdistance;
		progvector.push(new com.makinolo.rlvforge.tacx.data.ProgramData(elapseddistance, prevslope));
	  
		// Create the new blocks for the pgfm program		
		// Infoblock for General Info
		var gi:GeneralInfo = new GeneralInfo(checkSum, this.stagename, GeneralInfo.SLOPE_PROGRAM,
					 		GeneralInfo.DISTANCE_PROGRAM, this.totaldistance, 
					 		0.0, altitudeStart, 0);
	
		var cpg:CatalystProgram =  new CatalystProgram();	
		cpg.setGeneralInfo(gi);
		if (progvector!=null)
			cpg.setProgramData(progvector);
	
		return cpg;	
	}
	
	public function toRlv():RLVDefinition
	{
		var newrlv:RLVDefinition = new RLVDefinition();
		var desc:String = "";
		var cinfo:CourseInfo;
		var sgstart:Boolean;
		// Create the new blocks for the rlv program						
		var framenumber:Number = 0;
		var distanceperframe:Number;
		var prevspeed:Number, speed:Number;
		var seccount:Number = 0;
		var elapseddistance:Number=0;
		var compdist:Number = 0;
		var prevframenumber:Number;
		var lasttime:Number=0;		
		var sgtable:Array = new Array();
		var exist:Boolean;
		// RLV frame distance mapping		
		var fdmlist:Array = new Array();
		var infoboxlist:Array =  new Array();		
		var cmdlist:String = "*C body {  font-family: Verdana, Arial, Helvetica, sans-serif; font-size:50px; background-color: #FFFFFF;text-align: center}\n";

		var videofile:String = stagename + ".avi";	
							
		prevspeed = pdatafixed[0].getSpeed();			
		prevframenumber = 0;
		var itime:Number = gpsdatafixed[0].getTime();
		// El primer mapping empieza en el frame 0 y es igual que el que empieza
		// en el frame 1. Esto parece ser una constante en todos los videos RLV

		for (var i:int=0; i<pdatafixed.length-1; i++)
		{
         	seccount = (gpsdatafixed[i].getTime()-itime)/1000;
			framenumber = Math.round(seccount * 25);
			speed = pdatafixed[i].getSpeed();
			compdist += speed * (seccount - lasttime);
			lasttime = seccount;
									
			// Comprobacion de infobox y segments
			if (gpsdatafixed[i].getComment() != null)
			{
				if (gpsdatafixed[i].getCommentType() == "INFOBOX" )
				{
					infoboxlist.push(new RLVInfoBox(framenumber, 1));
					desc = gpsdatafixed[i].getComment();					
					var time:int = (desc.length/5);
					time = time<5?5:time;
					cmdlist += "TEXT(\""+desc+"\");HIDE(\""+ time +"\");\n";
				}
				else
				{
					var segname:String;
					var footprint:String = gpsdatafixed[i].getCommentType();
					if (footprint.substring(0,8) == "SEGSTART")
					{
						sgstart = true;
						segname = footprint.substring(9);
					}
					else
					{
						sgstart = false;	
						segname = footprint.substring(7);
					}

					cinfo = null;
					// Buscamos el segmento por si ya está
					exist = false;
					for (var j:int = 0; j< sgtable.length; j++)
					{
						if (sgtable[j].compareCourseSegmentName( segname))
						{
							exist = true;					
							cinfo = sgtable[j];
						}
					}
					if (cinfo == null)
					{						
						cinfo = new CourseInfo(0,0,segname,gpsdatafixed[i].getComment());
					}							
					
					if (sgstart)
						cinfo.setStart(elapseddistance);
					else
						cinfo.setEnd(elapseddistance);				
					
					// Solo meto en el array el cinfo si acabo de crearlo
					if (!exist)
						sgtable.push(cinfo);			
				}
			}							
	        // *** Fin de comprobacion de infobox y segments	        	
	  
			if (prevspeed != speed)
			{
				// Añadimos dos mappings en cada cambio de velocidad
				// uno para el frame anterior y otro para el actual menos uno
				// de tal modo que conseguimos un mapeo de velocidad constante entre
				// esos dos frames
	        	distanceperframe = prevspeed / 25;			
				fdmlist.push(new RLVFrameDistanceMapping(prevframenumber, distanceperframe));
				fdmlist.push(new RLVFrameDistanceMapping(framenumber-1, distanceperframe));
				prevframenumber = framenumber;
				prevspeed = speed;				
			}
			elapseddistance = pdatafixed[i].getDistance();
	    }
						
        // calculamos el ultimo
        distanceperframe = prevspeed / 25;					
		fdmlist.push(new RLVFrameDistanceMapping(prevframenumber, distanceperframe));
	        
	    elapseddistance = pdatafixed[i].getDistance();
		newrlv.setFdmapping(fdmlist);
			
		// RLV Video info block						
		var rlvinf:RLVInfo = new RLVInfo(videofile, 25, 0, 0);		
		newrlv.setRLVinfo(rlvinf);
	
		// RLV Infoboxes
		newrlv.setInfobox(infoboxlist);
		newrlv.setInfoboxText(cmdlist);
		
		// RLV Courses Info
		var ciarray:Array = new Array();

		ciarray[0] = new CourseInfo(0, this.totaldistance, "Total Trip", "");			
			
		// Check the segments table for segments with 0 end position
		// and change to total distance			
		for(var k:int=0; k<sgtable.length; k++)
		{			
			cinfo = sgtable[k]; 
			if (cinfo.getEnd() == 0)
				cinfo.setEnd(this.totaldistance);
			ciarray.push( cinfo);
		}								
		newrlv.setCourseinfo(ciarray);
		
		return newrlv;			
	}	
	
	public function toGpx():String
	{		
		var newTrack:String = '<?xml version="1.0" encoding="UTF-8"?>\n' + 
							  '<gpx creator="RLV Forge - BKOOL - http://www.bkool.com" version="1.1" xmlns="http://www.topografix.com/GPX/1/1">\n' +
    						  '<trk>\n' +       
							  "<name>"+this.stagename+"</name>\n" + 							  
        		    		  '<desc>'+this.stagename+'</desc>\n' +
        				      '<trkseg>\n';

		for(var i:int=0; i<this.gpsdatafixed.length-1; i++)		 			    
		{
			var currtime:Number;
			var prevtime:Number; 
			// Discard points with the same time compared at decs of a second
			if (i>0)
			{
				currtime = Math.round(gpsdatafixed[i].getTime()/100);
				prevtime = Math.round(gpsdatafixed[i-1].getTime()/100);
			}  
			else
			{
				currtime = 0;
				prevtime = -1;
			}
			if (currtime!=prevtime)
			{
				newTrack += "<trkpt lat='"+gpsdatafixed[i].getLat()+"' lon='"+gpsdatafixed[i].getLon()+"'>\n" + 
							"<ele>"+gpsdatafixed[i].getElev()+"</ele>\n" +  
							"<time>"+Utils.formatDate(new Date(gpsdatafixed[i].getTime()))+"</time>\n" +
	             			"</trkpt>\n";
	  		}
		}		
        newTrack +=  '</trkseg>\n' +
    				 '</trk>\n' + 
					 '</gpx>\n';        			
		
		return newTrack;	
	}

	public function convertNMEA2XML(file:FileStream):XML
	{
		
		var ret:XML = 	<gpx version="1.0" creator="RLVForge" 
						  xmlns="http://www.topografix.com/GPX/1/0"
						  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
						  xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd">	
						</gpx>;
												
		var seg:XML = <trkseg/>;		

		var lines:Array = file.readUTFBytes(file.bytesAvailable).split(/\n/);
		var dataDate:String = null;
		var formatter:DateFormatter = new DateFormatter();
		formatter.formatString = "YYYY-MM-DDTJJ:NN:SS,QQZ";
		for each(var line:String in lines)
		{
          line = StringUtil.trim(line);
          if (line.length > 3)
          {
              var fields:Array = line.split(",");
              var sentence:String = fields[0];
              if (sentence == '$GPRMC' && fields[9]!="" && fields[9].length==6)
              {              	  
               	  dataDate = fields[9];
              }
              else if (sentence == '$GPGGA' && Number(fields[7]) >=4 && Number(fields[6])>=1)
              {
                  var point:XML = <trkpt></trkpt>;
                  // Lat & lon come in NMEA format DDMM.MMMM,{N|S|W|E}
                  // Where DD are decimal degrees and MM.MMMM are minutes
                  point.@lat = GISPoint.parseDeg(fields[2]+fields[3]);
                  point.@lon = GISPoint.parseDeg(fields[4]+fields[5]);
                  if (fields[9] != '')
                    	point.ele = Number(fields[9]);
                  if (dataDate!=null)
                  {                  	
                  	var day = Number(dataDate.substr(0,2));
                  	var month = Number(dataDate.substr(2,2));
                  	var year = Number(dataDate.substr(4,2));
                  	var hour = Number(fields[1].substr(0,2));
                  	var minute = Number(fields[1].substr(2,2));
                  	var second = Number(fields[1].substr(4,2));
                  	var millis = Number(fields[1].substr(7,2));
                  	point.time = formatter.format(new Date(year+2000, month-1, day, hour, minute, second, millis));
                  }
                  if (point.latitude && point.longitude && dataDate!=null)
                  {															
					seg.appendChild(point);
				  }				  
              }
          }
        }
		lines = null;
		var track:XML = <trk/>;
		track.appendChild(seg);
		ret.appendChild(track);
		return ret;
	}
}

}